package com.angle3d;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;

import android.opengl.GLES20;

/**
 * Container class for vertices and indices forming a mesh.
 * @author Alessandro Boccalatte
 */
public class VertexBuffer {

	private static final int SHORT_SIZE = 2;
	private static final int FLOAT_SIZE = 4;
	
	/**
	 * Class representing a vertex attribute, such as position, normal, texture coordinate, color or any other user-defined attribute.
	 * @author Alessandro Boccalatte
	 */
	public static class VertexAttribute {
		private final String mName;
		private final int mCompCount;
		
		/**
		 * Creates a VertexAttribute with the given name and number of components.
		 * The attribute name must match the one appearing in the source code of the vertex shader that will be used for rendering.  
		 * @param name the attribute name (matching the shader attribute name)
		 * @param components the number of components of the attribute (e.g. 1 for a float attribute, 2 for a vec2 attribute, etc.)
		 */
		public VertexAttribute(String name, int components) {
			mName = name;
			mCompCount = components;
		}
		
		public String getName() { return mName; }
		public int getComponentCount() { return mCompCount; }
		public int getByteCount() { return mCompCount * FLOAT_SIZE; }
		
		public static final VertexAttribute DEFAULT_POSITION = new VertexAttribute("Position", 3);
		public static final VertexAttribute DEFAULT_NORMAL = new VertexAttribute("Normal", 3);
		public static final VertexAttribute DEFAULT_TEXCOORD = new VertexAttribute("TexCoord", 2);
		public static final VertexAttribute DEFAULT_COLOR = new VertexAttribute("Color", 4);
	}
	
	
	private final ByteBuffer mVertices;
	private final ShortBuffer mIndices;
	private final int mIndexCount;
	private final int mVertexCount;
	private final VertexAttribute[] mAttribs;
	
	/**
	 * Creates and initializes a VertexBuffer instance for the specified number of indices, vertices and vertex attributes. 
	 * @param indexCount the number of indices
	 * @param vertexCount the number of vertices
	 * @param attributes the applicable vertex attributes (e.g. position, normal, ...)
	 * @throws IllegalArgumentException if no attributes are passed to the constructor (at least one is required)
	 */
	public VertexBuffer(int indexCount, int vertexCount, VertexAttribute... attributes) {
		if (attributes == null || attributes.length == 0) {
			throw new IllegalArgumentException("Vertex attribute count must be at least 1");
		}
		mIndexCount = indexCount;
		mVertexCount = vertexCount;
		mAttribs = attributes;
		
		//initialize indices
		ByteBuffer ibb = ByteBuffer.allocateDirect(mIndexCount * SHORT_SIZE);
		ibb.order(ByteOrder.nativeOrder());
	    mIndices = ibb.asShortBuffer();  
	    mIndices.position(0); 
		
	    //initialize vertices
	    int byteSize = getVertexSize() * mVertexCount;
	    mVertices = ByteBuffer.allocateDirect(byteSize);
	    mVertices.order(ByteOrder.nativeOrder());
	    mVertices.position(0);
	}
	
	/**
	 * Sets the array of indices.
	 * Note: if the length of the array is greater than the index count of this VertexBuffer, extra indices are just ignored.
	 * @param indices an array of indices.
	 */
	public void setIndices(short[] indices) {
		for (int i = 0; i < Math.min(mIndexCount, indices.length); ++i) {
			mIndices.put(indices[i]);
		}
		mIndices.position(0);
	}
	
	/**
	 * Sets the array of vertices.
	 * Note: if the length of the array is greater than the size of this VertexBuffer, extra vertices are just ignored.
	 * @param vertices an array of vertices.
	 */
	public void setVertices(float[] vertices) {
		int n = Math.min(vertices.length, mVertexCount * getVertexSize() / FLOAT_SIZE);
		for (int i = 0; i < n; ++i) {
			mVertices.putFloat(vertices[i]);
		}
	    mVertices.position(0);
	}
	
	/**
	 * Returns the size of a vertex (sum of all attributes, e.g. position + normal + ...) in bytes.
	 * @return the size of a vertex in bytes.
	 */
	public int getVertexSize() {
		int size = 0;
		for (VertexAttribute va : mAttribs) {
			size += va.getByteCount();
		}
		return size;
	}
	
	/**
	 * Returns the number of indices. 
	 * @return the number of indices.
	 */
	public int getIndexCount() {
		return mIndexCount;
	}
	
	/**
	 * Returns the number of vertices.
	 * @return the number of vertices.
	 */
	public int getVertexCount() {
		return mVertexCount;
	}
	
	/**
	 * Returns the buffer containing the indices.
	 * @return the buffer containing the indices.
	 */
	public ShortBuffer getIndices() {
		return mIndices;
	}
	
	/**
	 * Prepares the vertex buffer pointers for rendering. 
	 * @param shader the shader program in which the vertex attributes are located.
	 */
	public void bind(ShaderProgram shader) {
		int offset = 0;
		int vertexSize = getVertexSize();
		for (VertexAttribute va : mAttribs) {
			int attribLoc = GLES20.glGetAttribLocation(shader.getProgramId(), va.getName());
			if (attribLoc >= 0) {
				mVertices.position(offset);
				GLES20.glVertexAttribPointer(attribLoc, va.getComponentCount(), GLES20.GL_FLOAT, false, vertexSize, mVertices);
		        GLES20.glEnableVertexAttribArray(attribLoc);
			}
	        offset += va.getByteCount();
		}
	}
	
	/**
	 * Unbinds the vertex buffer. 
	 * @param shader the shader program in which the vertex attributes are located.
	 */
	public void unbind(ShaderProgram shader) {
		for (VertexAttribute va : mAttribs) {
			int attribLoc = GLES20.glGetAttribLocation(shader.getProgramId(), va.getName());
			if (attribLoc >= 0) {
				GLES20.glDisableVertexAttribArray(attribLoc);
			}    
		}
	}
}


